{
  "nbformat": 4,
  "nbformat_minor": 0,
  "metadata": {
    "colab": {
      "name": "6.7. 门控循环单元（GRU）.ipynb",
      "provenance": [],
      "collapsed_sections": [],
      "include_colab_link": true
    },
    "kernelspec": {
      "name": "python3",
      "display_name": "Python 3"
    },
    "accelerator": "GPU"
  },
  "cells": [
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "view-in-github",
        "colab_type": "text"
      },
      "source": [
        "<a href=\"https://colab.research.google.com/github/chongzicbo/Dive-into-Deep-Learning-tf.keras/blob/master/6.7.%20%E9%97%A8%E6%8E%A7%E5%BE%AA%E7%8E%AF%E5%8D%95%E5%85%83%EF%BC%88GRU%EF%BC%89.ipynb\" target=\"_parent\"><img src=\"https://colab.research.google.com/assets/colab-badge.svg\" alt=\"Open In Colab\"/></a>"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "gvfFSTDktaL6",
        "colab_type": "text"
      },
      "source": [
        "##6.7. 门控循环单元（GRU）\n",
        "上一节介绍了循环神经网络中的梯度计算方法。我们发现，当时间步数较大或者时间步较小时，循环神经网络的梯度较容易出现衰减或爆炸。虽然裁剪梯度可以应对梯度爆炸，但无法解决梯度衰减的问题。通常由于这个原因，循环神经网络在实际中较难捕捉时间序列中时间步距离较大的依赖关系。\n",
        "\n",
        "门控循环神经网络（gated recurrent neural network）的提出，正是为了更好地捕捉时间序列中时间步距离较大的依赖关系。它通过可以学习的门来控制信息的流动。其中，门控循环单元（gated recurrent unit，GRU）是一种常用的门控循环神经网络 [1, 2]。另一种常用的门控循环神经网络则将在下一节中介绍。\n",
        "\n",
        "###6.7.1. 门控循环单元\n",
        "下面将介绍门控循环单元的设计。它引入了重置门（reset gate）和更新门（update gate）的概念，从而修改了循环神经网络中隐藏状态的计算方式。\n",
        "\n",
        "####6.7.1.1. 重置门和更新门\n",
        "如图6.4所示，门控循环单元中的重置门和更新门的输入均为当前时间步输入 $X_t$ 与上一时间步隐藏状态 $H_{t−1}$ ，输出由激活函数为sigmoid函数的全连接层计算得到。\n",
        "\n",
        "<img src=\"https://zh.gluon.ai/_images/gru_1.svg\" width=\"500\"/>\n",
        "\n",
        "<center>图 6.4 门控循环单元中重置门和更新门的计算</center>\n",
        "\n",
        "具体来说，假设隐藏单元个数为 $h$ ，给定时间步 $t$的小批量输入$\\boldsymbol{X}_t \\in \\mathbb{R}^{n \\times d}$（样本数为 $n$ ，输入个数为 $d$ ）和上一时间步隐藏状态$\\boldsymbol{H}_{t-1} \\in \\mathbb{R}^{n \\times h}$。重置门 $\\boldsymbol{R}_t \\in \\mathbb{R}^{n \\times h}$和更新门$\\boldsymbol{Z}_t \\in \\mathbb{R}^{n \\times h}$的计算如下：\n",
        "\n",
        "$$\\begin{split}\\begin{aligned}\n",
        "\\boldsymbol{R}_t = \\sigma(\\boldsymbol{X}_t \\boldsymbol{W}_{xr} + \\boldsymbol{H}_{t-1} \\boldsymbol{W}_{hr} + \\boldsymbol{b}_r),\\\\\n",
        "\\boldsymbol{Z}_t = \\sigma(\\boldsymbol{X}_t \\boldsymbol{W}_{xz} + \\boldsymbol{H}_{t-1} \\boldsymbol{W}_{hz} + \\boldsymbol{b}_z),\n",
        "\\end{aligned}\\end{split}$$\n",
        "其中 $\\boldsymbol{W}_{xr}, \\boldsymbol{W}_{xz} \\in \\mathbb{R}^{d \\times h}$和$\\boldsymbol{W}_{hr}, \\boldsymbol{W}_{hz} \\in \\mathbb{R}^{h \\times h}$是权重参数，$\\boldsymbol{b}_r, \\boldsymbol{b}_z \\in \\mathbb{R}^{1 \\times h}$是偏差参数。“多层感知机”一节中介绍过，sigmoid函数可以将元素的值变换到0和1之间。因此，重置门$\\boldsymbol{R}_t$和更新门$\\boldsymbol{Z}_t$中每个元素的值域都是 [0,1] 。\n",
        "####6.7.1.2. 候选隐藏状态\n",
        "接下来，门控循环单元将计算候选隐藏状态来辅助稍后的隐藏状态计算。如图6.5所示，我们将当前时间步重置门的输出与上一时间步隐藏状态做按元素乘法（符号为 $\\odot$ ）。如果重置门中元素值接近0，那么意味着重置对应隐藏状态元素为0，即丢弃上一时间步的隐藏状态。如果元素值接近1，那么表示保留上一时间步的隐藏状态。然后，将按元素乘法的结果与当前时间步的输入连结，再通过含激活函数tanh的全连接层计算出候选隐藏状态，其所有元素的值域为$[-1, 1]$ 。\n",
        "<img src=\"https://zh.gluon.ai/_images/gru_2.svg\" width=\"500\"/>\n",
        "\n",
        "<center>图 6.5 门控循环单元中候选隐藏状态的计算。这里的 $\\odot$是按元素乘法</center>\n",
        "\n",
        "具体来说，时间步 $t$的候选隐藏状态$\\tilde{\\boldsymbol{H}}_t \\in \\mathbb{R}^{n \\times h}$的计算为\n",
        "$$\n",
        "\\tilde{\\boldsymbol{H}}_t = \\text{tanh}(\\boldsymbol{X}_t \\boldsymbol{W}_{xh} + \\left(\\boldsymbol{R}_t \\odot \\boldsymbol{H}_{t-1}\\right) \\boldsymbol{W}_{hh} + \\boldsymbol{b}_h),\n",
        "$$\n",
        "其中 $\\boldsymbol{W}_{xh} \\in \\mathbb{R}^{d \\times h}$和$\\boldsymbol{W}_{hh} \\in \\mathbb{R}^{h \\times h}$是权重参数，$\\boldsymbol{b}_h \\in \\mathbb{R}^{1 \\times h}$是偏差参数。从上面这个公式可以看出，重置门控制了上一时间步的隐藏状态如何流入当前时间步的候选隐藏状态。而上一时间步的隐藏状态可能包含了时间序列截至上一时间步的全部历史信息。因此，重置门可以用来丢弃与预测无关的历史信息。\n",
        "\n",
        "####6.7.1.3. 隐藏状态\n",
        "最后，时间步 $t$ 的隐藏状态$\\boldsymbol{H}_t \\in \\mathbb{R}^{n \\times h}$的计算使用当前时间步的更新门 $\\boldsymbol{Z}_t$来对上一时间步的隐藏状态 $H_{t−1}$ 和当前时间步的候选隐藏状态 \n",
        "\n",
        "$$\n",
        "\\boldsymbol{H}_t = \\boldsymbol{Z}_t \\odot \\boldsymbol{H}_{t-1}  + (1 - \\boldsymbol{Z}_t) \\odot \\tilde{\\boldsymbol{H}}_t.\n",
        "$$\n",
        "\n",
        "<img src=\"https://zh.gluon.ai/_images/gru_3.svg\" width=\"500\"/>\n",
        "\n",
        "<center>图 6.6 门控循环单元中隐藏状态的计算。这里的$\\odot$是按元素乘法</center>\n",
        "\n",
        "值得注意的是，更新门可以控制隐藏状态应该如何被包含当前时间步信息的候选隐藏状态所更新，如图6.6所示。假设更新门在时间步$t'$到$t$(t' < t)之间一直近似1。那么，在时间步$t'$到$t$之间的输入信息几乎没有流入时间步 $t$ 的隐藏状态 $H_t$ 。实际上，这可以看作是较早时刻的隐藏状态$\\boldsymbol{H}_{t'-1}$一直通过时间保存并传递至当前时间步 $t$ 。这个设计可以应对循环神经网络中的梯度衰减问题，并更好地捕捉时间序列中时间步距离较大的依赖关系。\n",
        "\n",
        "我们对门控循环单元的设计稍作总结：\n",
        "\n",
        "* 重置门有助于捕捉时间序列里短期的依赖关系；\n",
        "* 更新门有助于捕捉时间序列里长期的依赖关系。\n",
        "\n",
        "###6.7.2. 读取数据集\n",
        "为了实现并展示门控循环单元，下面依然使用周杰伦歌词数据集来训练模型作词。这里除门控循环单元以外的实现已在“循环神经网络”一节中介绍过。以下为读取数据集部分。"
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "_UqiFexHaOqn",
        "colab_type": "code",
        "outputId": "06c48e59-aaa9-4366-fb75-9a82b39b6e73",
        "colab": {
          "base_uri": "https://localhost:8080/",
          "height": 62
        }
      },
      "source": [
        "%matplotlib inline\n",
        "import math\n",
        "import tensorflow as tf\n",
        "import numpy as np\n",
        "from IPython import display\n",
        "import matplotlib.pyplot as plt\n",
        "from tensorflow import keras\n",
        "from tensorflow.keras import losses\n",
        "from tensorflow.data import Dataset\n",
        "import time\n",
        "import random\n",
        "import zipfile"
      ],
      "execution_count": 0,
      "outputs": [
        {
          "output_type": "display_data",
          "data": {
            "text/html": [
              "<p style=\"color: red;\">\n",
              "The default version of TensorFlow in Colab will soon switch to TensorFlow 2.x.<br>\n",
              "We recommend you <a href=\"https://www.tensorflow.org/guide/migrate\" target=\"_blank\">upgrade</a> now \n",
              "or ensure your notebook will continue to use TensorFlow 1.x via the <code>%tensorflow_version 1.x</code> magic:\n",
              "<a href=\"https://colab.research.google.com/notebooks/tensorflow_version.ipynb\" target=\"_blank\">more info</a>.</p>\n"
            ],
            "text/plain": [
              "<IPython.core.display.HTML object>"
            ]
          },
          "metadata": {
            "tags": []
          }
        }
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "kLFOJOKTaYs5",
        "colab_type": "code",
        "colab": {}
      },
      "source": [
        "tf.enable_eager_execution()"
      ],
      "execution_count": 0,
      "outputs": []
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "zVuM4TgyaaYb",
        "colab_type": "code",
        "outputId": "d47a4ab5-256a-483e-f220-9d11a7b4e29b",
        "colab": {
          "base_uri": "https://localhost:8080/",
          "height": 121
        }
      },
      "source": [
        "def load_data_jay_lyrics():\n",
        "  from google.colab import drive\n",
        "  drive.mount('/content/drive')\n",
        "  with zipfile.ZipFile('/content/drive/My Drive/data/d2l-zh-tensoflow/jaychou_lyrics.txt.zip')as zin:\n",
        "    with zin.open('jaychou_lyrics.txt') as f:\n",
        "      corpus_chars=f.read().decode('utf-8')\n",
        "  corpus_chars=corpus_chars.replace('\\n',' ').replace('\\r',' ')\n",
        "  corpus_chars=corpus_chars[0:10000]\n",
        "  idx_to_char=list(set(corpus_chars))\n",
        "  char_to_idx=dict([(char,i) for i,char in enumerate(idx_to_char)])\n",
        "  vocab_size=len(char_to_idx)\n",
        "  corpus_indices=[char_to_idx[char] for char in corpus_chars]\n",
        "  return corpus_indices,char_to_idx,idx_to_char,vocab_size\n",
        "\n",
        "(corpus_indices,char_to_idx,idx_to_char,vocab_size)=load_data_jay_lyrics() "
      ],
      "execution_count": 0,
      "outputs": [
        {
          "output_type": "stream",
          "text": [
            "Go to this URL in a browser: https://accounts.google.com/o/oauth2/auth?client_id=947318989803-6bn6qk8qdgf4n4g3pfee6491hc0brc4i.apps.googleusercontent.com&redirect_uri=urn%3aietf%3awg%3aoauth%3a2.0%3aoob&response_type=code&scope=email%20https%3a%2f%2fwww.googleapis.com%2fauth%2fdocs.test%20https%3a%2f%2fwww.googleapis.com%2fauth%2fdrive%20https%3a%2f%2fwww.googleapis.com%2fauth%2fdrive.photos.readonly%20https%3a%2f%2fwww.googleapis.com%2fauth%2fpeopleapi.readonly\n",
            "\n",
            "Enter your authorization code:\n",
            "··········\n",
            "Mounted at /content/drive\n"
          ],
          "name": "stdout"
        }
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "yQobDExzzgnT",
        "colab_type": "text"
      },
      "source": [
        "###6.7.3. 从零开始实现\n",
        "我们先介绍如何从零开始实现门控循环单元。\n",
        "\n",
        "####6.7.3.1. 初始化模型参数\n",
        "下面的代码对模型参数进行初始化。超参数num_hiddens定义了隐藏单元的个数。"
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "GcadT-a4acS7",
        "colab_type": "code",
        "colab": {}
      },
      "source": [
        "num_inputs,num_hiddens,num_outputs=vocab_size,256,vocab_size\n",
        "def get_params():\n",
        "  def _one(shape):\n",
        "    return tf.Variable(tf.random.normal(stddev=0.01,shape=shape))\n",
        "\n",
        "  def _three():\n",
        "    return (_one((num_inputs,num_hiddens)),\n",
        "        _one((num_hiddens,num_hiddens)),\n",
        "        tf.Variable(tf.zeros(num_hiddens)))\n",
        "    \n",
        "  W_xz,W_hz,b_z=_three() #更新们参数\n",
        "  W_xr,W_hr,b_r=_three() #重置门参数\n",
        "  W_xh,W_hh,b_h=_three() #候选隐藏状态参数\n",
        "\n",
        "  #输出层参数\n",
        "  W_hq=_one((num_hiddens,num_outputs))\n",
        "  b_q=tf.Variable(tf.zeros(num_outputs))\n",
        "\n",
        "  params = [W_xz, W_hz, b_z, W_xr, W_hr, b_r, W_xh, W_hh, b_h, W_hq, b_q]\n",
        "  return params"
      ],
      "execution_count": 0,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "VrOFA7BUzl5k",
        "colab_type": "text"
      },
      "source": [
        "####6.7.3.2. 定义模型\n",
        "下面的代码定义隐藏状态初始化函数init_gru_state。同“循环神经网络的从零开始实现”一节中定义的init_rnn_state函数一样，它返回由一个形状为(批量大小, 隐藏单元个数)的值为0的Tensor组成的元组。"
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "1vRlKWZHcBL7",
        "colab_type": "code",
        "colab": {}
      },
      "source": [
        "def init_gru_state(batch_size,num_hiddens):\n",
        "  return (tf.zeros(shape=(batch_size,num_hiddens)),)"
      ],
      "execution_count": 0,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "jME_veipzwIi",
        "colab_type": "text"
      },
      "source": [
        "下面根据门控循环单元的计算表达式定义模型。"
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "oTiqDPkRcYwG",
        "colab_type": "code",
        "colab": {}
      },
      "source": [
        "def gru(inputs,state,params):\n",
        "  W_xz, W_hz, b_z, W_xr, W_hr, b_r, W_xh, W_hh, b_h, W_hq, b_q=params\n",
        "  H,=state\n",
        "  outputs=[]\n",
        "  for X in inputs:\n",
        "    Z=tf.sigmoid(tf.matmul(X,W_xz)+tf.matmul(H,W_hz)+b_z) #更新门\n",
        "    R=tf.sigmoid(tf.matmul(X,W_xr)+tf.matmul(H,W_hr)+b_r) #重置门\n",
        "    H_tilda=tf.tanh(tf.matmul(X,W_xh)+tf.matmul(R*H,W_hh)+b_h)\n",
        "    H=Z*H+(1-Z)*H_tilda\n",
        "    Y=tf.matmul(H,W_hq)+b_q\n",
        "    outputs.append(Y)\n",
        "  return outputs,(H,)"
      ],
      "execution_count": 0,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "tVPDx-Oxz6AA",
        "colab_type": "text"
      },
      "source": [
        "####6.7.3.3. 训练模型并创作歌词\n",
        "我们在训练模型时只使用相邻采样。设置好超参数后，我们将训练模型并根据前缀“分开”和“不分开”分别创作长度为50个字符的一段歌词。"
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "TNKtQffMdpbK",
        "colab_type": "code",
        "colab": {}
      },
      "source": [
        "num_epochs, num_steps, batch_size, lr, clipping_theta = 160, 35, 32, 1e2, 1e-2\n",
        "pred_period, pred_len, prefixes = 40, 50, ['分开', '不分开']"
      ],
      "execution_count": 0,
      "outputs": []
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "GIF3wiA0eZ-E",
        "colab_type": "code",
        "colab": {}
      },
      "source": [
        "def to_onehot(X,size):\n",
        "  return [tf.one_hot(x,size) for x in tf.transpose(X)]"
      ],
      "execution_count": 0,
      "outputs": []
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "y0gfqiOgdtGE",
        "colab_type": "code",
        "colab": {}
      },
      "source": [
        "def predict_rnn(prefix,num_chars,rnn,params,init_rnn_state,num_hiddens,vocab_size,idx_to_char,char_to_idx):\n",
        "  state=init_rnn_state(1,num_hiddens)\n",
        "  output=[char_to_idx[prefix[0]]]\n",
        "  for t in range(num_chars+len(prefix)-1):\n",
        "    #将上一时间步的输出作为当前时间步的输入\n",
        "    X=to_onehot(tf.reshape(tf.constant([output[-1]]),shape=(1,1)),vocab_size)\n",
        "    # print(X[0].shape)\n",
        "    #计算输出和更新隐藏状态\n",
        "    (Y,state)=rnn(X,state,params)\n",
        "    #下一个时间步的输入是prefix里的字符或者当前的最佳预测字符\n",
        "    if t<len(prefix)-1:\n",
        "      output.append(char_to_idx[prefix[t+1]])\n",
        "    else:\n",
        "      output.append(tf.argmax(Y[0],axis=1).numpy()[0])\n",
        "  return ''.join([idx_to_char[i] for i in output])"
      ],
      "execution_count": 0,
      "outputs": []
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "CdkHEloLd9WP",
        "colab_type": "code",
        "colab": {}
      },
      "source": [
        "def data_iter_random(corpus_indices,batch_size,num_steps):\n",
        "  #减1是因为输出的索引是相应输入的索引加1\n",
        "  num_examples=(len(corpus_indices)-1)//num_steps\n",
        "  epoch_size=num_examples//batch_size\n",
        "  example_indices=list(range(num_examples))\n",
        "  random.shuffle(example_indices)\n",
        "\n",
        "  #返回从pos开始的长为num_steps的序列\n",
        "  def _data(pos):\n",
        "    return corpus_indices[pos:pos+num_steps]\n",
        "\n",
        "  for i in range(epoch_size):\n",
        "    #每次读取batch_size个随机样本\n",
        "    i=i*batch_size\n",
        "    batch_indices=example_indices[i:i+batch_size]\n",
        "    X=[_data(j*num_steps) for j in batch_indices]\n",
        "    Y=[_data(j*num_steps+1) for j in batch_indices]\n",
        "    yield tf.constant(X),tf.constant(Y)\n",
        "\n",
        "def data_iter_consecutive(corpus_indices,batch_size,num_steps):\n",
        "  corpus_indices=tf.constant(corpus_indices)\n",
        "  data_len=len(corpus_indices)\n",
        "  batch_len=data_len//batch_size\n",
        "  indices=tf.reshape(corpus_indices[0:batch_size*batch_len],shape=(batch_size,batch_len))\n",
        "  epoch_size=(batch_len-1) // num_steps\n",
        "  for i in range(epoch_size):\n",
        "    i=i*num_steps\n",
        "    X=indices[:,i:i+num_steps]\n",
        "    Y=indices[:,i+1:i+num_steps+1]\n",
        "    yield X,Y    "
      ],
      "execution_count": 0,
      "outputs": []
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "69cMY1r_eGiZ",
        "colab_type": "code",
        "colab": {}
      },
      "source": [
        "def sgd(params,l,t,lr,batch_size,theta):\n",
        "  norm=tf.constant([0],dtype=tf.float32)\n",
        "  for param in params:\n",
        "    dl_dp=t.gradient(l,param)\n",
        "    if dl_dp is None:\n",
        "      print(param,dl_dp)\n",
        "    norm+=tf.reduce_sum((dl_dp**2))\n",
        "  norm=tf.sqrt(norm).numpy()\n",
        "  if norm>theta:\n",
        "    for param in params:\n",
        "      dl_dp=t.gradient(l,param) #求梯度\n",
        "      dl_dp=tf.assign(tf.Variable(dl_dp),dl_dp*theta/norm) #裁剪梯度\n",
        "      param.assign_sub(lr*dl_dp/batch_size) #更新梯度"
      ],
      "execution_count": 0,
      "outputs": []
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "UZ842VnfeINQ",
        "colab_type": "code",
        "colab": {}
      },
      "source": [
        "def train_and_predict_rnn(rnn,get_params,init_rnn_state,num_hiddens,vocab_size,corpus_indices,idx_to_char,char_to_idx,is_random_iter,num_epochs,\n",
        "                          num_steps,lr,clipping_theta,batch_size,pred_period,pred_len,prefixes):\n",
        "  if is_random_iter:\n",
        "    data_iter_fn=data_iter_random\n",
        "  else:\n",
        "    data_iter_fn=data_iter_consecutive\n",
        "\n",
        "  params=get_params()\n",
        "  loss=losses.SparseCategoricalCrossentropy(from_logits=True)\n",
        "\n",
        "  for epoch in range(num_epochs):\n",
        "    if not is_random_iter:#如果使用相邻采样，在epoch开始时初始化隐藏状态\n",
        "      state=init_rnn_state(batch_size,num_hiddens)\n",
        "    l_sum,n,start=0.0,0,time.time()\n",
        "    data_iter=data_iter_fn(corpus_indices,batch_size,num_steps)\n",
        "    for X,Y in data_iter:\n",
        "      if is_random_iter:#如果使用相邻采样，在每个小批量更新前初始化隐藏状态\n",
        "        state=init_rnn_state(batch_size,num_hiddens)\n",
        "      else:#否则需要使用detach函数从\n",
        "        state=tf.stop_gradient(state) #停止计算该张量的梯度\n",
        "      with tf.GradientTape(persistent=True) as t:\n",
        "        t.watch(params)\n",
        "        inputs=to_onehot(X,vocab_size)\n",
        "        # outputs有num_steps个形状为(batch_size, vocab_size)的矩阵\n",
        "        (outputs,state)=rnn(inputs,state,params)\n",
        "        # 拼接之后形状为(num_steps * batch_size, vocab_size)\n",
        "        outputs=tf.concat(values=[*outputs],axis=0)\n",
        "        # Y的形状是(batch_size, num_steps)，转置后再变成长度为\n",
        "        # batch * num_steps 的向量，这样跟输出的行一一对应\n",
        "        y=tf.reshape(tf.transpose(Y),shape=(-1,))\n",
        "        #使用交叉熵损失计算平均分类误差\n",
        "        l=tf.reduce_mean(loss(y,outputs))\n",
        "      sgd(params,l,t,lr,1,clipping_theta) #因为误差已经取过均值了,所以batch_size为1\n",
        "      l_sum+=l.numpy()*y.numpy().shape[0]\n",
        "      n+=y.numpy().shape[0]\n",
        "    if(epoch +1)%10==0:\n",
        "      print('epoch %d, perplexity %f, time %.2f sec' % (\n",
        "                epoch + 1, l_sum / n, time.time() - start))\n",
        "      for prefix in prefixes:\n",
        "          print(' -', predict_rnn(\n",
        "              prefix, pred_len, rnn, params, init_rnn_state,\n",
        "              num_hiddens, vocab_size, idx_to_char, char_to_idx))"
      ],
      "execution_count": 0,
      "outputs": []
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "t2x6QDq8ePpT",
        "colab_type": "code",
        "outputId": "f91b57a3-052b-4b38-c6ae-269bd4dd4bf0",
        "colab": {
          "base_uri": "https://localhost:8080/",
          "height": 823
        }
      },
      "source": [
        "train_and_predict_rnn(gru,get_params,init_gru_state,num_hiddens,vocab_size,corpus_indices,idx_to_char,char_to_idx,False,num_epochs,num_steps,lr,clipping_theta,batch_size,pred_period,pred_len,prefixes)"
      ],
      "execution_count": 0,
      "outputs": [
        {
          "output_type": "stream",
          "text": [
            "epoch 10, perplexity 5.705591, time 9.95 sec\n",
            " - 分开 我我我我我我我我我我我我我我我我我我我我我我我我我我我我我我我我我我我我我我我我我我我我我我我我我\n",
            " - 不分开我我我我我我我我我我我我我我我我我我我我我我我我我我我我我我我我我我我我我我我我我我我我我我我我我我\n",
            "epoch 20, perplexity 5.573065, time 10.10 sec\n",
            " - 分开 我我我的 我我我的 我我我的 我我我的 我我我的 我我我的 我我我的 我我我的 我我我的 我我我的\n",
            " - 不分开 我我我的 我我我的 我我我的 我我我的 我我我的 我我我的 我我我的 我我我的 我我我的 我我我的\n",
            "epoch 30, perplexity 5.339191, time 10.29 sec\n",
            " - 分开 我不不 我不你的我 我不不 我不你的我 我不不 我不你的我 我不不 我不你的我 我不不 我不你的我\n",
            " - 不分开 我不不的我 我不不 我不你的我 我不不 我不你的我 我不不 我不你的我 我不不 我不你的我 我不不\n",
            "epoch 40, perplexity 5.010512, time 10.20 sec\n",
            " - 分开 我想你的让我爱爱女 爱爱女人 我想你的可爱女人 爱爱的让我爱女女 爱爱女人 我想你的可爱女人 爱爱\n",
            " - 不分开 我想你的让我爱爱女 爱爱女人 我想你的可爱女人 爱爱的让我爱女女 爱爱女人 我想你的可爱女人 爱爱\n",
            "epoch 50, perplexity 4.675587, time 9.81 sec\n",
            " - 分开 我想你的爱爱人 爱爱女人 我想想你的可爱女人 坏坏的让我疯狂的可爱女人 坏坏的让我疯狂的可爱女人 \n",
            " - 不分开 我想你的爱爱女人  爱有你的爱爱人 爱爱女人 我不要你的可爱女人 坏坏的让我疯狂的可爱女人 坏坏的\n",
            "epoch 60, perplexity 4.306026, time 9.73 sec\n",
            " - 分开 我想要你的爱爱  爱有你的爱写 一场人 我不要 我不要 我不要 我不要 我不要 我不要 我不要 我\n",
            " - 不分开 我想你的爱爱 一场人 我不要 我不要 我不要 我不要 我不要 我不要 我不要 我不要 我不要 我不\n",
            "epoch 70, perplexity 3.936894, time 9.90 sec\n",
            " - 分开 我想要你的微笑 不知不觉 我不要再想 我不要这样 我不要这样 我不要这样 我不要这样 我不要这样 \n",
            " - 不分开 我想要你的微笑 不知不觉 我不要再想 我不要这样 我不要这样 我不要这样 我不要这样 我不要这样 \n",
            "epoch 80, perplexity 3.499446, time 9.78 sec\n",
            " - 分开 我想要你的微笑 一定在人人 我想要你的微笑 一定在人人 我想要你 我不要再想 我不能 我不再再想 \n",
            " - 不分开 爱你在美不多 想要你这样的怒火 我想想你 我不要再想 我不 我不 我不 我不 我不 我不 我不 我\n",
            "epoch 90, perplexity 3.056026, time 9.77 sec\n",
            " - 分开 我想要这样很着你的手不放开 爱可不可以简简单单单 深埋在美索不达米亚平平             \n",
            " - 不分开 不知不觉 我不要再想 我不要再想 我不 我不 我不 我不 我不 我不 我不 我不 我不 我不 我不\n",
            "epoch 100, perplexity 2.620613, time 9.95 sec\n",
            " - 分开 我想要这样牵着你的手不放开 爱可不可以简简单平平 几原  爱情的太旧 闪烁茶忆 如人的美栈 我的完\n",
            " - 不分开  没有你在我有多难熬                                       \n",
            "epoch 110, perplexity 2.193252, time 9.94 sec\n",
            " - 分开我 你的话不面口 不知不觉 我跟了这节奏 后知后觉 我该了这生活 后知后觉 我该好好生活 我该好好生\n",
            " - 不分开 不知不觉 我跟了这节奏 后知后觉 我该了这生活 后知后觉 我该好好生活 我该好好生活 让我想你 你\n",
            "epoch 120, perplexity 1.780893, time 9.78 sec\n",
            " - 分开 我想要这样牵着你的手不放开 爱可不可以简简单单没有伤害 你 靠着我的肩膀 你 在我胸口睡著 像这样\n",
            " - 不分开 不知不觉 我跟了这节奏 后知后觉 又过了一个 后后后觉 我不能好生 我不能好生 我不能好生 我不能\n",
            "epoch 130, perplexity 1.403266, time 9.67 sec\n",
            " - 分开 我想要你的微笑每天都能看到  我知道这里很美但家乡的你更美 我来来这样坦堡 你说想和你到到 想要和\n",
            " - 不分开 不知不觉 你已经离开我 不知不觉 我跟了这节奏 后知后觉 又过了一个秋 后知后觉 我该好好生活 我\n",
            "epoch 140, perplexity 1.074328, time 9.81 sec\n",
            " - 分开 一直走 你在我的肩婆 从小伦着 你的完空 一直悲中 我想就这样牵着你的手不放开 爱可不可以简简单单\n",
            " - 不分开 不要再这样打我妈妈 我说的话你甘会听 不要再这样打我妈妈 难道你手不会痛吗 我该了这样打我妈妈 难\n",
            "epoch 150, perplexity 0.781095, time 9.72 sec\n",
            " - 分开 一个走 是谁都有 你一场霜 每一定一热 老唱盘 旧皮箱 装满了明屋片的铁盒里藏著一片玫瑰花瓣 黄金\n",
            " - 不分开 不知再这不打 不知不觉 我跟了这节奏 后知后觉 又过了一个秋 后知后觉 我该好好生活 我该好好生活\n",
            "epoch 160, perplexity 0.569501, time 10.29 sec\n",
            " - 分开 小小的假 干谁变风  从果着你 别我的脚婆是一句就是 就是开我 你不是一个奏 后知后觉 我该好好生\n",
            " - 不分开 不知再这不打 不知不觉 我跟了这节奏 后知后觉 又过了一个秋 后知后觉 我该好好生活 我该好好生活\n"
          ],
          "name": "stdout"
        }
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "nkSXJsxieyua",
        "colab_type": "text"
      },
      "source": [
        "###6.7.4. 小结\n",
        "* 门控循环神经网络可以更好地捕捉时间序列中时间步距离较大的依赖关系。\n",
        "* 门控循环单元引入了门的概念，从而修改了循环神经网络中隐藏状态的计算方式。它包括重置门、更新门、候选隐藏状态和隐藏状态。\n",
        "* 重置门有助于捕捉时间序列里短期的依赖关系。\n",
        "* 更新门有助于捕捉时间序列里长期的依赖关系。"
      ]
    }
  ]
}